package ar.edu.itba.tp1.gps;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import ar.edu.itba.tp1.gps.api.GPSProblem;
import ar.edu.itba.tp1.gps.api.GPSProblemImpl;
import ar.edu.itba.tp1.gps.board.ArithmeticsRestrictions;
import ar.edu.itba.tp1.gps.board.Bucket;
import ar.edu.itba.tp1.gps.board.Coordinate;
import ar.edu.itba.tp1.gps.board.Operators;
import ar.edu.itba.tp1.gps.heuristics.GPSProblemFirstHeuristic;
import ar.edu.itba.tp1.gps.heuristics.GPSProblemSecondHeuristic;
import ar.edu.itba.tp1.gps.heuristics.GPSProblemThirdHeuristic;

public class GPSRun {
	public static Integer n;
	private static final Logger LOGGER = Logger.getLogger(GPSRun.class);
	private static final Pattern restrictionPattern = Pattern.compile("\\{([^\\}]+)\\}"); 
	
	public static void main(String[] args) {
		// Invoca al engine
        if (args.length == 0) {
        	LOGGER.error("No configuration file!");
            return;
        }
        Properties prop = new Properties();
        String fileName = args[0];
        InputStream is = null;
        try {
        	is = new FileInputStream(fileName);
        } catch (FileNotFoundException e) {
            LOGGER.error("File: " + fileName + " doesn't exist!");
            System.exit(1);
        }

        try {
            prop.load(is);
        } catch (IOException e) {
            LOGGER.error("Cannot load configuration's file properties!");
            System.exit(1);
        }
            
        SearchStrategy searchStrategy = assignSearchStrategy(prop.getProperty("searchStrategy"));
        if (searchStrategy == null) {
        	LOGGER.error("Invalid Search Strategy!");
            System.exit(1);
        }
        
        GPSRun.n = assignBoardSize(prop.getProperty("N"));
        if (GPSRun.n == null) {
        	LOGGER.error("Invalid value of N!");
            System.exit(1);
        }

        ArithmeticsRestrictions arithmeticsRestrictions = loadArithmeticsRestrictions(prop.getProperty("arithmeticsRestrictions"));
        if (arithmeticsRestrictions == null) {
        	LOGGER.error("Invalid arithmetics Restrictions!");
            System.exit(1);
        }
        
        GPSProblem problem;
        if (searchStrategy == SearchStrategy.AStar || searchStrategy == SearchStrategy.GS) {
	        problem = assignHeuristic(prop.getProperty("heuristic"), arithmeticsRestrictions);
	        if (problem == null) {
	        	LOGGER.error("Invalid value of heuristic!");
	            System.exit(1);
	        }
        } else {
        	problem = new GPSProblemImpl(arithmeticsRestrictions);
        }
        
        LOGGER.info("Properties loaded! Starting engine...");
        
        Long startTime = System.currentTimeMillis();
        
        GPSEngine calcuDokuEngine = new CalcuDokuEngine();
        calcuDokuEngine.engine(problem, searchStrategy);
        
        Long endTime = System.currentTimeMillis();
        LOGGER.info("Elapsed time: " + (endTime - startTime) + " ms");
	}
	
	private static GPSProblem assignHeuristic(String heuristicName, ArithmeticsRestrictions arithmeticsRestrictions) {
		if (heuristicName != null && heuristicName.length() > 0) {
			if (heuristicName.trim().equals("1")) {
				return new GPSProblemFirstHeuristic(arithmeticsRestrictions);
			} else if (heuristicName.trim().equals("2")) {
				return new GPSProblemSecondHeuristic(arithmeticsRestrictions);
			} else if (heuristicName.trim().equals("3")) {
				return new GPSProblemThirdHeuristic(arithmeticsRestrictions);
			}
		}
		return null;
	}

	private static ArithmeticsRestrictions loadArithmeticsRestrictions(String arithmeticsRestrictions) {
		ArithmeticsRestrictions restrictions = new ArithmeticsRestrictions();
		Matcher m = restrictionPattern.matcher(arithmeticsRestrictions);
		List<Coordinate> groupCells = new LinkedList<Coordinate>();
		while (m.find()) {
			Integer groupResult = null;
			Operators operator = null;
			String restriction = m.group(1);
			String[] restrictionProperties = restriction.split(",");
			for (String property: restrictionProperties) {
				if (property.matches("\\(\\d+;\\d+\\)")) {
					String parsedProperty = property.replaceAll("[()]", "");
					String[] coordinates = parsedProperty.split(";");
					Coordinate coordinate = new Coordinate(Integer.valueOf(coordinates[0]), Integer.valueOf(coordinates[1]));
					groupCells.add(coordinate);
				} else if (property.matches("\\d+")) {
					groupResult = Integer.valueOf(property);
				} else if (property.matches("(?:ADD)|(?:PROD)")) {
					operator = Operators.valueOf(property);
				} else {
					return null;
				}
			}
			Bucket bucket = buildBucket(groupCells, groupResult, operator);
			if (bucket == null) {
				return null;
			}
			for (Coordinate coordinate: groupCells){
				restrictions.addBucket(bucket, coordinate);
			}
			groupCells = new LinkedList<Coordinate>();
		}
		return restrictions;
	}

	private static Bucket buildBucket(List<Coordinate> groupCells, Integer groupResult, Operators operator) {
		if (groupCells == null || groupResult == null || operator == null || groupCells.isEmpty()) {
			return null;
		}
		return new Bucket(groupResult, groupCells, operator);
	}

	private static Integer assignBoardSize(String boardSize) {
		try {
			return Integer.valueOf(boardSize);
		} catch (NumberFormatException e) {
			return null;
		}
	}

	private static SearchStrategy assignSearchStrategy(String searchStrategy) {
        if (searchStrategy.toUpperCase().equals("DFS")) {
        	return SearchStrategy.DFS;
        } else if (searchStrategy.toUpperCase().equals("BFS")) {
            return SearchStrategy.BFS;
        } else if (searchStrategy.toUpperCase().equals("PI")) {
            return SearchStrategy.PI;
        } else if (searchStrategy.toUpperCase().equals("ASTAR")) {
        	return SearchStrategy.AStar;
        } else if (searchStrategy.toUpperCase().equals("GS")) {
        	return SearchStrategy.GS;
        }
        return null;
	}
}

